
import com.degrafa.core.IGraphicsFill;
import com.degrafa.core.IGraphicsStroke;
import com.degrafa.core.collections.FillCollection;
import com.degrafa.core.collections.GeometryCollection;
import com.degrafa.core.collections.StrokeCollection;
import com.degrafa.geometry.Geometry;
import com.degrafa.states.State;
import com.degrafa.states.StateManager;
import com.degrafa.triggers.ITrigger;

import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.geom.Rectangle;

import mx.events.PropertyChangeEventKind;
import mx.events.PropertyChangeEvent;

private var _data:Object;

/**
 * Allows a short hand property setting that is
 * specific to and parsed by each geometry object.
 * Look at the various geometry objects to learn what
 * this setting requires.
 **/
public function get data():Object
{
    return _data;
}

public function set data(value:Object):void
{
    _data = value;
}

private var _stroke:IGraphicsStroke;

/**
 * Defines the stroke object that will be used for
 * rendering this geometry object.
 **/
public function get stroke():IGraphicsStroke
{
    return _stroke;
}

public function set stroke(value:IGraphicsStroke):void
{
    _stroke = value;

}

private var _fill:IGraphicsFill;

/**
 * Defines the fill object that will be used for
 * rendering this geometry object.
 **/
public function get fill():IGraphicsFill
{
    return _fill;
}

public function set fill(value:IGraphicsFill):void
{
    _fill = value;

}

private var _fills:FillCollection;

[Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsFill")]
[ArrayElementType("com.degrafa.core.IGraphicsFill")]
/**
 * A array of IGraphicsFill objects.
 **/
public function get fills():Array
{
    initFillsCollection();
    return _fills.items;
}

public function set fills(value:Array):void
{
    initFillsCollection();
    _fills.items = value;
}

/**
 * Access to the Degrafa fill collection object for this graphic object.
 **/
public function get fillCollection():FillCollection
{
    initFillsCollection();
    return _fills;
}

/**
 * Initialize the collection by creating it and adding an event listener.
 **/
private function initFillsCollection():void
{
    if (!_fills)
    {
        _fills = new FillCollection();

        //add a listener to the collection
        if (enableEvents)
        {
            _fills.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler);
        }
    }
}

private var _strokes:StrokeCollection;

[Inspectable(category="General", arrayType="com.degrafa.core.IGraphicsStroke")]
[ArrayElementType("com.degrafa.core.IGraphicsStroke")]
/**
 * A array of IStroke objects.
 **/
public function get strokes():Array
{
    initSrokesCollection();
    return _strokes.items;
}

public function set strokes(value:Array):void
{
    initSrokesCollection();
    _strokes.items = value;

}

/**
 * Access to the Degrafa stroke collection object for this graphic object.
 **/
public function get strokeCollection():StrokeCollection
{
    initSrokesCollection();
    return _strokes;
}

/**
 * Initialize the collection by creating it and adding an event listener.
 **/
private function initSrokesCollection():void
{
    if (!_strokes)
    {
        _strokes = new StrokeCollection();

        //add a listener to the collection
        if (enableEvents)
        {
            _strokes.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler);
        }
    }
}

private var _geometry:GeometryCollection;

[Inspectable(category="General", arrayType="com.degrafa.IGeometryComposition")]
[ArrayElementType("com.degrafa.IGeometryComposition")]
/**
 * A array of IGeometryComposition objects.
 **/
public function get geometry():Array
{
    initGeometryCollection();
    return _geometry.items;
}

public function set geometry(value:Array):void
{

    initGeometryCollection();
    _geometry.items = value;

    for each (var item:Geometry in _geometry.items)
    {
        item.autoClearGraphicsTarget = false;
        item.graphicsTarget = [this];
    }

}

/**
 * Access to the Degrafa geometry collection object for this geometry object.
 **/
public function get geometryCollection():GeometryCollection
{
    initGeometryCollection();
    return _geometry;
}

/**
 * Initialize the geometry collection by creating it and adding an event listener.
 **/
private function initGeometryCollection():void
{
    if (!_geometry)
    {
        _geometry = new GeometryCollection();

        //add a listener to the collection
        if (enableEvents)
        {
            _geometry.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler);
        }
    }
}


/**
 * Principle event handler for any property changes to a
 * graphic object or it's child objects.
 **/
private function propertyChangeHandler(event:PropertyChangeEvent):void
{
    dispatchEvent(event);
    invalidateDisplayList();
}

//not required here but need for interface
/**
 * @private
 **/
public function get percentWidth():Number
{
    return 0;
}

public function set percentWidth(value:Number):void
{
}

/**
 * @private
 **/
public function get percentHeight():Number
{
    return 0;
}

public function set percentHeight(value:Number):void
{
}

/**
 * @private
 **/
public function get target():DisplayObjectContainer
{
    return null;
}

public function set target(value:DisplayObjectContainer):void
{
}

/**
 * @private
 **/
public function set graphicsData(value:Array):void
{
}

public function get graphicsData():Array
{
    return null;
}


/**
 * Ends the draw phase for geometry objects.
 *
 * @param graphics The current Graphics context being drawn to.
 **/
public function endDraw(graphics:Graphics):void
{
    if (fill)
    {
        fill.end(this.graphics);
    }
}

/**
 * Begins the draw phase for graphic objects. All graphic objects
 * override this to do their specific rendering.
 *
 * @param graphics The current context to draw to.
 * @param rc A Rectangle object used for fill bounds.
 **/
public function draw(graphics:Graphics, rc:Rectangle):void
{

    if (!parent)
    {
        return;
    }

    this.graphics.clear();

    if (geometry)
    {
        for each (var geometryItem:Geometry in _geometry.items)
        {

            if (geometryItem.state == "" || geometryItem.state == null)
            {
                if (states.length != 0)
                {
                    prepareState();
                }
                geometryItem.draw(this.graphics, null);
            }
            else
            {
                var possibleStates:Array = geometryItem.state.split(" ");
                if (possibleStates.length > 0)
                {
                    for (var i:int = 0; i < possibleStates.length; i++)
                    {
                        if (name == possibleStates[i])
                        {

                            geometryItem.draw(this.graphics, null);
                            break;
                        }
                    }
                }
                else if (geometryItem.state == name)
                {
                    geometryItem.draw(this.graphics, null);
                }
            }
        }
    }
}

[Bindable]
public var skinWidth:Number = 0;

[Bindable]
public var skinHeight:Number = 0;

/**
 * Draws the object and/or sizes and positions its children.
 **/
private function mixin_updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
    skinWidth = unscaledWidth;
    skinHeight = unscaledHeight;

    draw(null, null);
    endDraw(null);
}

/**********************************************************
 * Trigger related.
 **********************************************************/

private var _triggers:Array = [];

[Inspectable(arrayType="com.degrafa.triggers.ITrigger")]
[ArrayElementType("com.degrafa.triggers.ITrigger")]
public function get triggers():Array
{
    return _triggers;
}

public function set triggers(items:Array):void
{
    _triggers = items;

    if (_triggers)
    {
        //make sure each item knows about it's manager
        for each (var trigger:ITrigger in _triggers)
        {
            trigger.triggerParent = this;
        }
    }

}

//because of the way skins have a differed creation we need to 
//set all the bindings for the triggers when the first item is created
//this means that all triggers in all states are initialized otherwise we 
//could never change state based on a trigger unless the state has been 
//previously visited. This also ensures that the event listener is only added one time
//but it will be triggered for each rule.
private function onAddedToStage(event:Event):void
{
    if (triggers.length != 0)
    {
        if (!Object(this)._bindingsByDestination)
        {
            return;
        }
        var bindings:Object = Object(this)._bindingsByDestination;
        for each (var trigger:ITrigger in triggers)
        {
            if (!trigger.source)
            {
                if (bindings[trigger.id + ".source"])
                {
                    bindings[trigger.id + ".source"].execute(trigger);
                }
            }
        }
    }

}


/**********************************************************
 * End Trigger related.
 **********************************************************/


/**********************************************************
 * State related.
 **********************************************************/
private var _currentState:String = "";

[Bindable("currentStateChange")]
public function get currentState():String
{
    return (stateManager) ? stateManager.currentState : "";
}

public function set currentState(value:String):void
{
    stateManager.currentState = value;
}

private var stateManager:StateManager;

private var _states:Array = [];

[Inspectable(arrayType="com.degrafa.states.State")]
[ArrayElementType("com.degrafa.states.State")]
public function get states():Array
{
    return _states;
}

public function set states(items:Array):void
{

    _states = items;

    if (items)
    {
        if (!stateManager)
        {
            stateManager = new StateManager(this)
            //make sure each item knows about it's manager
            for each (var state:State in _states)
            {
                state.stateManager = stateManager;
            }
        }
    }
    else
    {
        stateManager = null;
    }

}


private var _state:String;

/**
 * The state at which to draw this object
 **/
public function get state():String
{
    return _state;
}

public function set state(value:String):void
{
    _state = value;
}

private var _stateEvent:String;

/**
 * The state event at which to draw this object
 **/
public function get stateEvent():String
{
    return _stateEvent;
}

public function set stateEvent(value:String):void
{
    _stateEvent = value;
}

//Only used in skin classes sets the matching current state 
//based on the name
private function prepareState():void
{
    //see if the state exists
    for each (var state:State in states)
    {
        if (state.name == name)
        {
            currentState = name;
        }
    }
}

/**********************************************************
 * END state related.
 **********************************************************/

/**********************************************************
 * event related.
 **********************************************************/
private var _enableEvents:Boolean = true;

/**
 * Enable events for this object.
 **/
[Inspectable(category="General", enumeration="true,false")]
public function get enableEvents():Boolean
{
    return _enableEvents;
}

public function set enableEvents(value:Boolean):void
{
    _enableEvents = value;
}

private var _suppressEventProcessing:Boolean = false;

/**
 * Temporarily suppress event processing for this object.
 **/
[Inspectable(category="General", enumeration="true,false")]
public function get suppressEventProcessing():Boolean
{
    return _suppressEventProcessing;
}

public function set suppressEventProcessing(value:Boolean):void
{

    if (_suppressEventProcessing == true && value == false)
    {
        _suppressEventProcessing = value;
        initChange("suppressEventProcessing", false, true, this);
    }
    else
    {
        _suppressEventProcessing = value;
    }
}

/**
 * Dispatches an event into the event flow.
 *
 * @see EventDispatcher
 **/
override public function dispatchEvent(event:Event):Boolean
{
    if (_suppressEventProcessing)
    {
        return false;
    }

    return (super.dispatchEvent(event));

}

/**
 * Dispatches an property change event into the event flow.
 **/
public function dispatchPropertyChange(bubbles:Boolean = false, property:Object = null, oldValue:Object = null, newValue:Object = null, source:Object =
                                       null):Boolean
{
    return dispatchEvent(new PropertyChangeEvent("propertyChange", bubbles, false, PropertyChangeEventKind.UPDATE, property, oldValue, newValue,
                                                 source));
}

/**
 * Helper function for dispatching property changes
 **/
public function initChange(property:String, oldValue:Object, newValue:Object, source:Object):void
{
    if (hasEventManager)
    {
        dispatchPropertyChange(false, property, oldValue, newValue, source);
    }
}

/**
 * Tests to see if a EventDispatcher instance has been created for this object.
 **/
public function get hasEventManager():Boolean
{
    return true;
}

public function get isInitialized():Boolean
{
    return true;
} 

/**********************************************************
 * END event related.
 **********************************************************/